Centralized Axios API Client (api.js)
The api.js file defines a centralized API client using Axios, serving as the foundation for all frontend HTTP requests. It standardizes configuration, retry metadata tracking, authentication, and global error normalization across the app
Responsibilities
-
Axios Configuration
- Sets the base URL for API requests.
- Configures timeout, headers, cookies, CSRF protection, max body size, and redirects.
-
Retry Logic
- Uses
axios-retry
to automatically retry requests on network errors or 5xx server responses. - Adds metadata (
retryCount
,startTime
) viaretryInterceptor
for debugging and tracking retries.
- Uses
-
Authentication
authInterceptor
attaches the access token to requests.- Handles route authorization and token refresh for expired access tokens.
-
Error Handling
errorInterceptor
normalizes backend errors into a consistent structure for the service layer.
Interceptors
The interceptors are attached in the following order: retryInterceptor
, authInterceptor
, errorInterceptor
. See the documentation below for more details about each interceptor.
Error Interceptor
The error interceptor is responsible for handling global errors that occur during Axios requests and ensuring all errors are normalized into a consistent shape.
It does not display UI notifications or handle presentation. Its only job is to intercept, categorize, and normalize errors into a consistent structure that can be safely used by services or passed to a centralized error handling utility in the UI.
Responsibilities
-
Catch errors from Axios responses (
response
and network-level errors). -
Classify errors into high-level types (
NETWORK_ERROR
,AUTH_ERROR
,FORBIDDEN
,API_ERROR
). -
Normalize error messages into an array (
messages: []
), regardless of backend format.- Uses
data.messages
if available. - Falls back to wrapping
data.message
in an array. - Defaults to
['An error occurred']
if neither exists.
- Uses
-
Provide consistent error shape for consumers (services, Redux thunks, components).
Error Types Handled
-
Network Errors (
NETWORK_ERROR
)- No response from server (e.g., server down, connection lost, CORS issue).
- Normalized shape:
{ type: 'NETWORK_ERROR', status: null, messages: ['Unable to reach the server'] }
-
Authentication Errors (
AUTH_ERROR
)- Response with
401 Unauthorized
(excluding login route). - Indicates expired/invalid session.
- Normalized with:
{ type: 'AUTH_ERROR', status: 401, messages: ['Authentication required'] }
- Response with
-
Authorization Errors (
FORBIDDEN
)- Response with
403 Forbidden
. - Indicates insufficient permissions.
- Normalized with:
{ type: 'FORBIDDEN', status: 403, messages: ['Permission denied'] }
- Response with
-
Backend API Errors (
API_ERROR
)- Any other
4xx
or5xx
response. - Normalized from
response.data.messages
orresponse.data.message
. - Always returned as an array of strings.
- Any other
Normalized Error Shape
Every error returned by the interceptor has the following structure:
{
type: 'NETWORK_ERROR' | 'AUTH_ERROR' | 'FORBIDDEN' | 'API_ERROR',
status: number | null, // HTTP status code (null for network errors)
messages: string[], // Always an array of strings
raw: any, // Original response data (if available)
originalError: AxiosError // The unmodified Axios error object
}
Auth Interceptor
The auth interceptor handles all authentication tasks for Axios requests. It automatically attaches tokens, refreshes expired tokens, and blocks requests to routes the user is not authorized to access.
Responsibilities
-
Attach Authorization Header
- Adds the access token (e.g.,
Bearer <token>
) automatically to every request.
- Adds the access token (e.g.,
-
Handle Token Refresh
- Intercepts
401 Unauthorized
responses caused by expired access tokens. - Calls the refresh token endpoint or refresh service to obtain a new token.
- Updates the stored access token (localStorage, cookies, or Redux store).
- Retries the original request automatically with the refreshed token.
- If refresh fails, the error is passed to the error interceptor for further handling.
- Intercepts
-
Route Authorization Check
- Blocks requests to endpoints that the current user does not have permission to access.
- Implements role/permission checks in the request interceptor.
-
Logging in Development Environment
- Logs request URLs, methods, or durations for debugging or analytics.
Example Flow
- UI Component calls
authService.someRequest()
. - The auth interceptor adds the access token to the request.
- Request is sent to the backend.
- Backend responds with
401 Unauthorized
if the token is expired. - Auth interceptor calls the refresh token endpoint and updates the stored token.
- The original request is retried automatically with the new token.
- If the retry succeeds, the response is returned to the service/component.
- If the retry fails, the error is passed to the
errorInterceptor
for normalization.
Retry Interceptor
The retry interceptor attaches metadata to Axios requests for tracking retry attempts and optionally logging them. It works together with axios-retry
, which performs the actual retry logic.
Responsibilities
-
Attach Retry Metadata
-
Adds a
metadata
object to each request containing:startTime
: timestamp when the request was sentretryCount
: current retry count fromaxios-retry
config (or 0)
-
-
Pass Responses Through
- Returns successful responses unchanged.
- Rejects errors after retries are exhausted.
-
Developer Debugging Hooks
- A placeholder for logging retry attempts, durations, or counts in development environment.
Example Flow
- Component or service calls
apiClient.someRequest()
. - Retry interceptor attaches
metadata
to the request. - Axios-retry evaluates if the request should be retried (network error or 5xx).
- If retried,
metadata.retryCount
is updated automatically. - If all retries succeed, response is returned.
- If retries fail, error proceeds to
errorInterceptor
for normalization.
Data Extractor Interceptor
The data extractor interceptor transforms successful API responses to simplify data access by unwrapping nested data
fields from the backend's response structure.
Responsibilities
-
Unwrap Nested Data
- For responses with
success: true
, extractsresponse.data.data
and sets it asresponse.data
to remove nesting.
- For responses with
-
Pass Errors Through
- Rejects errors unchanged, allowing the
errorInterceptor
to normalize them.
- Rejects errors unchanged, allowing the
Example Flow
- Backend returns
{ success: true, message: 'OK', data: [payload], errors: [] }
. - Data extractor interceptor checks
response.data.success
. - If
success: true
, setsresponse.data = [payload]
. - If
success: false
or error, passes the response/error to the next interceptor. - Services (e.g.,
landingZoneMonitoringService
) receiveresponse.data
as the payload directly.
Notes
- Assumes consistent backend response structure (
{ success, data, message, errors }
). - Applied after
authInterceptor
andretryInterceptor
, beforeerrorInterceptor
.